﻿using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Nibble.Umb.Locator.GeoCoding.Services;
using Nibble.Umb.Locator.GeoCoding;
using Nibble.Umb.Locator.GeoCoding.Services.Google;
using System.Xml;
using System.Xml.Xsl;
using System.ComponentModel;
using umbraco;

namespace Nibble.Umb.Locator.Usercontrols
{
    public partial class Locator : System.Web.UI.UserControl
    {
        /// <summary>
        /// Handles the Load event of the Page control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                try
                {
                    if (Request["s"] != null && Request["s"] != string.Empty)
                    {

                        litMultipleResults.Text = getCaption(MultipleResultsCaption);                        

                        litResults.Text = string.Empty;
                        pnlMultiple.Visible = false;

                        IGeoCoder geoCoder = new GoogleGeoCoder(Utility.GetGoogleMapsKey());
                        Address[] addresses = geoCoder.GeoCode(Request["s"] + AddressExtra);
                        if (addresses.Length == 0)//no results
                        {
                            litResults.Text = umbraco.macro.GetXsltTransformResult(umbraco.content.Instance.XmlContent, umbraco.macro.getXslt(ResultsXslt));  
                        }
                        else
                        {
                            if (addresses.Length > 1)//multiple results, need to choose first
                            {
                                rblLocations.Items.Clear();
                                foreach (Address address in addresses)
                                {
                                    rblLocations.Items.Add(new ListItem(address.ToString(), address.Coordinates.Latitude.ToString(Utility.NumberFormatInfo) + "," + address.Coordinates.Longitude.ToString(Utility.NumberFormatInfo)));
                                }
                                pnlMultiple.Visible = true;
                            }
                            else// single result, ok show results
                            {
                                ShowResult(addresses[0].ToString(), new Location(addresses[0].Coordinates.Latitude, addresses[0].Coordinates.Longitude));
                            }
                        }
                    }

                }
                catch(Exception ex) { litResults.Text = "Error displaying locator results"; }
            }
        }

        /// <summary>
        /// Handles the Click event of the btnChooseMultiple control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected void btnChooseMultiple_Click(object sender, EventArgs e)
        {
            pnlMultiple.Visible = false;
            ShowResult(rblLocations.SelectedItem.Text, new Location(
                Convert.ToDouble(rblLocations.SelectedValue.Split(',')[0], Utility.NumberFormatInfo),
                 Convert.ToDouble(rblLocations.SelectedValue.Split(',')[1], Utility.NumberFormatInfo)));
        }

        #region Methods

        /// <summary>
        /// Shows the result.
        /// </summary>
        /// <param name="locationName">Name of the location.</param>
        /// <param name="location">The location.</param>
        private void ShowResult(string locationName, Location location)
        {
            bool useLegacySchema = true;
            try { useLegacySchema = Utility.UseLegacySchema; }catch (MissingMethodException) { }

            string propertySyntax = useLegacySchema ? "data [@alias = '{0}']" : "{0}";


            DistanceUnits distanceunit = DistanceUnits.Miles;
            if (UnitInKm == true)
                distanceunit = DistanceUnits.Kilometers;            

            XmlDocument content = new XmlDocument(); //umbraco.content.Instance.XmlContent;
           
            bool contentLoaded = false;
            while (contentLoaded == false)
            {
                try
                {
                    content.Load(Server.MapPath(umbraco.GlobalSettings.ContentXML));
                    contentLoaded = true;
                }
                catch
                {
                    System.Threading.Thread.Sleep(100);
                }
            }

            XmlNode root = content.SelectSingleNode("/root");

            //search node
            XmlElement locationNode = content.CreateElement("location");
            locationNode.InnerText = location.ToString().Replace(" ","");
            XmlAttribute nameAttribute = content.CreateAttribute("name");
            nameAttribute.Value = locationName;
            locationNode.Attributes.Append(nameAttribute);
            XmlAttribute latitudeAttribute = content.CreateAttribute("latitude");
            latitudeAttribute.Value = location.Latitude.ToString();
            locationNode.Attributes.Append(latitudeAttribute);
            XmlAttribute longitudeAttribute = content.CreateAttribute("longitude");
            longitudeAttribute.Value = location.Longitude.ToString();
            locationNode.Attributes.Append(longitudeAttribute);
            XmlAttribute unitAttribute = content.CreateAttribute("unit");
            unitAttribute.Value = distanceunit.ToString().ToLower();
            locationNode.Attributes.Append(unitAttribute);
            

            double lowestDistance = -1;
            double highestDistance = -1;
            
            //distance nodes

            if (useLegacySchema)
            {
                XPath = XPath.Replace("$currentPage",
                    string.Format("/root//node [@id = {0}]", umbraco.presentation.nodeFactory.Node.GetCurrent().Id));
            }
            else
            {
                XPath = XPath.Replace("$currentPage",
                    string.Format("/root//* [@isDoc and @id = {0}]", umbraco.presentation.nodeFactory.Node.GetCurrent().Id));
            }


            XmlNodeList nodes = content.SelectNodes(XPath);
            foreach (XmlNode node in nodes)
            {
                try
                {

                    Location nodeLocation = new Location();

                    if (!UseAddress)
                    {
                        //documents have coordinates stored
                        nodeLocation = new Location(
                            Convert.ToDouble(node.SelectSingleNode(string.Format(propertySyntax, LocationAlias)).InnerText.Split(',')[0], Utility.NumberFormatInfo),
                            Convert.ToDouble(node.SelectSingleNode(string.Format(propertySyntax, LocationAlias)).InnerText.Split(',')[1], Utility.NumberFormatInfo));
                    }
                    else
                    {
                        //fetch coordinates by address
                        string nodeAddress = string.Empty;

                        foreach (string addressAlias in AddressAliases.Split(';'))
                        {
                            nodeAddress += node.SelectSingleNode(string.Format(propertySyntax, addressAlias)).InnerText + ", ";
                        }

                        nodeAddress = nodeAddress.Substring(0, nodeAddress.Length > 0 ? nodeAddress.Length - 2: 0);
                        
                        IGeoCoder geoCoder = new GoogleGeoCoder(Utility.GetGoogleMapsKey());
                        Address[] addresses = geoCoder.GeoCode(nodeAddress);

                        if (addresses.Length == 1)
                        {
                            nodeLocation = new Location(addresses[0].Coordinates.Latitude,
                                addresses[0].Coordinates.Longitude);
                        }
                    }

                    double distance = location.DistanceBetween(nodeLocation, distanceunit);
                    if (distance < lowestDistance || lowestDistance == -1)
                        lowestDistance = distance;
                    if (distance > highestDistance || highestDistance == -1)
                        highestDistance = distance;


                    XmlElement distanceNode = 
                        content.CreateElement(useLegacySchema ? "data" : "distance");
                    distanceNode.InnerText = distance.ToString(Utility.NumberFormatInfo);
                    if (useLegacySchema)
                    {
                        XmlAttribute aliasAttribute = content.CreateAttribute("alias");
                        aliasAttribute.Value = "distance";
                        distanceNode.Attributes.Append(aliasAttribute);
                    }
                    node.AppendChild(distanceNode);


                    if (UseAddress)
                    {
                        XmlElement addresslocationNode = content.CreateElement(useLegacySchema ? "data" : "addresslocation");
                        addresslocationNode.InnerText = nodeLocation.Latitude + "," + nodeLocation.Longitude;
                        if (useLegacySchema)
                        {
                            XmlAttribute addressLocationAttribute = content.CreateAttribute("alias");
                            addressLocationAttribute.Value = "addresslocation";
                            addresslocationNode.Attributes.Append(addressLocationAttribute);
                        }
                        node.AppendChild(addresslocationNode);
                    }
                }
                catch { }
            }

            XmlAttribute lowestDistanceAttribute = content.CreateAttribute("lowestDistance");
            lowestDistanceAttribute.Value = lowestDistance.ToString();
            locationNode.Attributes.Append(lowestDistanceAttribute);

            XmlAttribute highestDistanceAttribute = content.CreateAttribute("highestDistance");
            highestDistanceAttribute.Value = highestDistance.ToString();
            locationNode.Attributes.Append(highestDistanceAttribute);

            root.AppendChild(locationNode);
            //transform
            litResults.Text = umbraco.macro.GetXsltTransformResult(content, umbraco.macro.getXslt(ResultsXslt));            
        }

        /// <summary>
        /// Gets the caption.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <returns></returns>
        private string getCaption(string text)
        {
            if (text.Length > 0)
            {
                if (text.Substring(0, 1) == "#")
                {
                   return umbraco.library.GetDictionaryItem(text.Substring(1,text.Length -1));
                }
                else
                {
                    return text;
                }
            }
            else
            {
                return text;
            }
        }

        #endregion

        #region Properties

        private string _xpath; 
        private string _locationalias = "location";
        private bool _useaddress = false;
        private string _addressaliases = string.Empty;
        private string _resultXslt = "LocatorResults.xslt";
        private string _addressExtra;
        private bool _unitInKm = false;
        private string _multipleResultsCaption = "There are multiple results, please choose:";

        public string XPath
        {
            get { return _xpath; }
            set { _xpath = value; }
        }

        [DefaultValue("location")]
        public string LocationAlias
        {
            get { return _locationalias; }
            set { _locationalias = value; }
        }

        [DefaultValue(false)]
        public bool UseAddress
        {
            get { return _useaddress; }
            set { _useaddress = value; }
        }

        public string AddressAliases
        {
            get { return _addressaliases; }
            set { _addressaliases = value; }
        }

        [DefaultValue("LocatorResults.xslt")]
        public string ResultsXslt
        {
            get { return _resultXslt; }
            set { _resultXslt = value; }
        }

        public string AddressExtra
        {
            get { return _addressExtra; }
            set { _addressExtra = value; }
        }

        [DefaultValue(false)]
        public bool UnitInKm
        {
            get { return _unitInKm; }
            set { _unitInKm = value; }
        }

        [DefaultValue("There are multiple results, please choose:")]
        public string MultipleResultsCaption
        {
            get { return _multipleResultsCaption; }
            set { _multipleResultsCaption = value; }
        }


        #endregion
    }
}